﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using AzukiMap.MapSource;
using AzukiMap.Map;
using Geotools.Geometries;
using AzukiMap.Renderer;
using System.Collections.Generic;
using AzukiMap.Tools.Logger;

namespace AzukiMap.Layer.Tile
{
    public class GsiLayer : TiledLayer
    {
        delegate double[] MoveTileGsi(double blLonInSec, double blLatInSec, double tileScale);

        List<MoveTileGsi> _moveTileMethodsGsi;
        GsiSource _tileSourceGsi;

        public GsiLayer(GsiSource tileSource, MapCanvas mapCanvas, string name)
            : base(tileSource, mapCanvas, name)
        {
            _moveTileMethodsGsi = new List<MoveTileGsi>();
            _tileSourceGsi = tileSource;
            SetTileMoveOrder();
        }

        void SetTileMoveOrder()
        {
            _moveTileMethodsGsi.Add(MoveTileUpGsi);
            _moveTileMethodsGsi.Add(MoveTileRightGsi);
            _moveTileMethodsGsi.Add(MoveTileDownGsi);
            _moveTileMethodsGsi.Add(MoveTileLeftGsi);
        }

        public override void Draw()
        {
            if (!_isVisible)
            {
                return;
            }
            if (!CheckScale())
            {
                return;
            }
            double tileScale = GetTileScale(_mapCanvas.GeoScale);
            GTPoint centerCoord = _mapCanvas.GeoCenter;
            GTCoordinate tlPoint = GetTileOrigin(centerCoord.X, centerCoord.Y, tileScale);
            double blLonInSec = tlPoint.X;
            double blLatInSec = tlPoint.Y;

            bool isAllTilesDrawed = false;
            bool needsMoreTile = false;
            int tilesOnSameDirection = 1;

            // Draw tiles in a spiral from center tile.
            while (!isAllTilesDrawed)
            {
                isAllTilesDrawed = true;
                foreach (MoveTileGsi moveTile in _moveTileMethodsGsi)
                {
                    for (int i = 0; i < tilesOnSameDirection; i++)
                    {
                        GTPolygon tileExtent = GetTileExtent(tileScale, blLonInSec, blLatInSec);
                        // Use tiles only intersects on a map canvas.
                        if (IsOnMapCanvas(tileExtent))
                        {
                            string baseUri = GetBaseReqString(tileScale);
                            string tileReqStr = CreateTileReqString(tileScale, (int)(blLonInSec * 100), (int)(blLatInSec * 100));
                            if (!IsRendered(tileReqStr))
                            {
                                TileData tile = CreateTile(baseUri, tileReqStr, tileExtent, tileScale, blLatInSec);
                                Logger.Debug(
                                    "GsiLayer.Draw():"
                                     + " reqString = " + tileReqStr
                                    );
                                _renderer.RenderTile(tile);
                                // TODO: add renderedTiles after image loaded.
                                _renderedTiles.Add(tileReqStr);
                            }
                            isAllTilesDrawed = false;
                        }
                        double[] tilePosition = moveTile(blLonInSec, blLatInSec, tileScale);
                        blLonInSec = tilePosition[0];
                        blLatInSec = tilePosition[1];
                    }
                    // Two directions use same tile number.
                    if (needsMoreTile)
                    {
                        tilesOnSameDirection++;
                        needsMoreTile = false;
                    }
                    else
                    {
                        needsMoreTile = true;
                    }
                }
            }
        }
        
        /// <summary>
        /// 
        /// http://cyberjapandata.gsi.go.jp/data/960/new/50400000/50400000-14400000-img.png
        /// 
        /// </summary>
        /// <param name="tileScaleInSec"></param>
        /// <param name="tileLeft"></param>
        /// <param name="tileTop"></param>
        /// <returns></returns>
        protected override string CreateTileReqString(double tileScaleInSec, int blLonInSec100, int blLatInSec100)
        {
            string reqStr = blLonInSec100.ToString() + "/" 
                + blLonInSec100.ToString() + "-" + blLatInSec100.ToString() + "-img.png";
            return reqStr;
        }

        string GetBaseReqString(double tileScaleInSec)
        {
            return _tileSourceGsi.BaseUris[tileScaleInSec];
        }

        protected override GTCoordinate GetGeoPointFromTileCoord(int tx, int ty, double tileScale)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Return GSI scale that is tile width in seconds.
        /// </summary>
        /// <param name="scale"></param>
        /// <returns></returns>
        protected override double GetTileScale(double scale)
        {
            double nearestScale = 0;
            foreach (KeyValuePair<double, double> tileScale in _tileSourceGsi.TileScalesRev)
            {
                if (tileScale.Key > scale)
                {
                    break;
                }
                nearestScale = tileScale.Value;
            }

            // if there is no scale to fit layer use the last scale in the list.
            if (nearestScale == 0)
            {
                foreach (KeyValuePair<double, double> tileScale in _tileSourceGsi.TileScalesRev)
                {
                    nearestScale = tileScale.Value;
                }

            }
            return nearestScale;
            
        }

        protected override GTCoordinate GetTileOrigin(double lon, double lat, double tileScale)
        {
            double originLonInSec = GetTileOriginLonInSec(lon, tileScale);
            double originLatInSec = GetTileOriginLatInSec(lat, tileScale);

            GTCoordinate tileOrigin = new GTCoordinate(originLonInSec, originLatInSec);
            return tileOrigin;
        }

        TileData CreateTile(string baseUri, string tileReqStr, GTPolygon tileExtent, double tileScale, double blLatInSec)
        {
            Uri tileUri = new Uri(baseUri + tileReqStr);
            int tileWidth = _tileSourceGsi.TileWidth[tileScale];
            int tileHeight = GetTileHeight(GetDegreeFromSecond(blLatInSec), tileWidth);

            TileData tile = new TileData(tileUri, tileExtent, tileWidth, tileHeight,
                 _tileSource.Srid, _tileSource.Dpi, tileScale, _tileSource.CoordUnit);
            return tile;
        }
        
        GTPolygon GetTileExtent(double tileScale, double leftInSec, double bottomInSec)
        {
            double blLon = GetDegreeFromSecond(leftInSec);
            double blLat = GetDegreeFromSecond(bottomInSec);
            double trLon = blLon + GetDegreeFromSecond(tileScale);
            double trLat = blLat + GetDegreeFromSecond(tileScale);

            return _geoUtil.CreateExtentPolygon(blLon, blLat, trLon, trLat);
        }

        double GetTileOriginLonInSec(double lon, double tileScale)
        {
            double sec = GetSecondFromDegree(lon);
            double tileXNum = Math.Floor(sec / tileScale);
            return tileScale * tileXNum;
        }

        double GetTileOriginLatInSec(double lat, double tileScale)
        {
            double sec = GetSecondFromDegree(lat);
            double tileYNum = Math.Floor(sec / tileScale);
            return tileScale * tileYNum;
        }

        double[] MoveTileUpGsi(double blLon, double blLat, double tileScale)
        {
            blLat = blLat + tileScale;
            return new double[2] { blLon, blLat };
        }

        double[] MoveTileRightGsi(double blLon, double blLat, double tileScale)
        {
            blLon = blLon + tileScale ;
            return new double[2] { blLon, blLat };
        }

        double[] MoveTileDownGsi(double blLon, double blLat, double tileScale)
        {
            blLat = blLat - tileScale;
            return new double[2] { blLon, blLat };
        }

        double[] MoveTileLeftGsi(double blLon, double blLat, double tileScale)
        {
            blLon = blLon - tileScale;
            return new double[2] { blLon, blLat };
        }

        int GetTileHeight(double blLat, double tileWidth)
        {
            return (int)Math.Floor(tileWidth / Math.Cos(blLat * Math.PI / 180.00));
        }

        double GetSecondFromDegree(double degree)
        {
            return degree * 60 * 60;
        }

        double GetDegreeFromSecond(double second)
        {
            return second / 60 / 60;
        }
    }
}
